/* Liviu Patrasco & Holly Beach
 * TCSS422 Project 1: Web Crawler
 * February 6, 2012
 */

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SpringLayout;
import javax.swing.Timer;

/**
 * The GUI Class contains the main method for running a web crawler application.
 * It provides the user the option to run the web crawler in either a 
 * single threaded mode or multi-threaded mode.  If the default URL is not used, 
 * The user must type a valid url in the format "http:\\www.website.com".
 * @author Liviu and Holly
 */
public class GUI extends JFrame {
	/**
	 * Does average links per page include all or only html and txt hrefs?
	 */
	private static final long serialVersionUID = -556570553660072178L;
	private JPanel menu_panel = new JPanel();
	private JPanel output_panel = new JPanel();
	private JRadioButton single_radio = new JRadioButton("Single Threaded");
	private JRadioButton multi_radio = new JRadioButton("Multi Threaded");
	private JTextField pages_field = new JTextField("5");

	//String of 10 keywords for testing
	private String key_words = "grapes, apples, beach, rock, house, standing, look, sun, oranges, diamonds";
	private JTextField keywords_field = new JTextField(key_words);

	private JLabel pages_label = new JLabel("Number of Pages: ", JLabel.TRAILING);
	private JLabel keywords_label = new JLabel("Keywords: ", JLabel.TRAILING);
	private JTextArea output_area = new JTextArea(20, 35);
	private final String START = "Start >>";
	private final String STOP = "Stop [ ]";
	private JCheckBox url_check_box = new JCheckBox("Use default URL");
	private JLabel url_label = new JLabel("URL: ", JLabel.TRAILING);
	private JTextField url_field = new JTextField();
	private final String DEFAULT_URL = "http://css.insttech.washington.edu/~mealden/";
	//private final String DEFAULT_URL = "http://www.cnn.com";
	private JButton start_button = new JButton();
	private Timer wait_timer;
	private StringBuilder running_string = new StringBuilder("oooooooooooooooooooooooooooooooo");
	private int running_index = 0;
	private MThreadedPageRetriever multi_retriever;
	private MThreadedPageAnalyzer multi_analyzer;
	private Thread retriever_thread;
	private Thread parser_thread;
	private Thread analyzer_thread;
	private boolean stopped = false;

	public GUI() { 
		setTitle("Web Crawler");
		setLayout(new BorderLayout());
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		menu_panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(1,1,1,1, Color.BLACK), "Menu"));
		menu_panel.setLayout(new SpringLayout());
		output_panel.setLayout(new BorderLayout());
		output_panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(1,1,1,1, Color.BLACK), "Result"));
		ButtonGroup radio_group = new ButtonGroup();
		radio_group.add(single_radio);
		radio_group.add(multi_radio);

		menu_panel.add(single_radio);
		menu_panel.add(multi_radio);

		menu_panel.add(pages_label);
		menu_panel.add(pages_field);

		menu_panel.add(keywords_label);
		menu_panel.add(keywords_field);

		menu_panel.add(keywords_label);
		menu_panel.add(keywords_field);

		menu_panel.add(url_label);
		menu_panel.add(url_field);

		menu_panel.add(new JLabel("", JLabel.TRAILING));
		menu_panel.add(url_check_box);

		url_check_box.setSelected(true);
		url_check_box.addActionListener(new checkBoxListener());
		url_field.setText(DEFAULT_URL);
		url_field.setForeground(Color.GRAY);
		url_field.setEditable(false);
		pages_label.setLabelFor(pages_field);
		keywords_label.setLabelFor(keywords_field);
		url_label.setLabelFor(url_field);
		SpringUtilities.makeCompactGrid(menu_panel, 5, 2, 0, 0, 10, 10);
		multi_radio.setSelected(true);
		output_area.setEditable(false);
		output_area.setFont(new Font("Courier", Font.PLAIN, 12));
		output_area.setWrapStyleWord(true);
		output_area.setLineWrap(true);
		JScrollPane pScroll = new JScrollPane(output_area, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		output_area.setMargin(new Insets(5,5,5,5)); 
		output_panel.add(pScroll, BorderLayout.NORTH);
		output_area.setAlignmentX(LEFT_ALIGNMENT);
		output_area.getAutoscrolls();
		output_panel.add(start_button, BorderLayout.PAGE_END);
		start_button.setText(START);
		start_button.addActionListener(new ButtonActionListener());
		add (menu_panel, BorderLayout.NORTH);
		add(output_panel, BorderLayout.CENTER);
		wait_timer = new Timer(50, new timerActionListener());
		pack();
		setSize(650, 650);
		setVisible(true);
	}

	/**
	 * controls the action of the timer string, giving the 
	 * illusion of a "thinking" program while the system
	 * is processing data.
	 */
	class timerActionListener implements ActionListener {
		public void actionPerformed (ActionEvent actionEvent) {
			running_string.setCharAt(running_index, 'O');
			int dash;
			if (running_index == 0) dash = running_string.length() - 1;
			else dash = Math.max(running_index-1, 0);
			running_string.setCharAt(dash, 'o');
			output_panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(1,1,1,1, Color.BLACK), running_string.toString()));
			running_index = (running_index + 1) % (running_string.length());
		}
	}

	class checkBoxListener implements ActionListener {
		public void actionPerformed(ActionEvent actionEvent) {
			if (url_check_box.isSelected()) {
				url_field.setEditable(false);
				url_field.setText(DEFAULT_URL);
				url_field.setForeground(Color.GRAY);
			} else {
				url_field.setEditable(true);
				url_field.setForeground(Color.BLACK);
			}
		}
	}

	class ButtonActionListener implements ActionListener {
		public void actionPerformed(ActionEvent actionEvent) {
			AbstractButton aButton = (AbstractButton) actionEvent.getSource();
			if (aButton.getText().equals(START)) {
				stopped = false;
				if (single_radio.isSelected()) {
					Runnable bee = new Runnable() {
						@Override
						public void run() {
							start_button.setText(STOP);
							wait_timer.start();
							crawl();
							wait_timer.stop();
							start_button.setText(START);
							output_panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(1,1,1,1, Color.BLACK), "Result"));
						}
					};
					new Thread(bee).start();
				} else {
					Runnable bee = new Runnable() {
						@Override
						public void run() {
							start_button.setText(STOP);
							wait_timer.start();
							multiCrawl();
							wait_timer.stop();
							start_button.setText(START);
							output_panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(1,1,1,1, Color.BLACK), "Result"));
						}
					};
					new Thread(bee).start();
				}
			} else {
				stopped = true;
				wait_timer.stop();
				output_panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(1,1,1,1, Color.BLACK), "Result"));
				start_button.setText(START);
				output_area.setText("EXECUTION STOPPED: ");
				if (multi_radio.isSelected())
						multi_retriever.stopExecution(true);
			}

		}
	};

	/** 
	 * Crawls the web starting at the url provided
	 * using a single thread.
	 */
	protected void crawl() {
		long start_time = System.currentTimeMillis();
		output_area.append("SingleThreaded Crawler Started...\n");
		PageParser pp = new PageParser(); 
		PageRetriever pr = new PageRetriever();
		int pages_requested = Integer.parseInt(pages_field.getText());

		String keys = (keywords_field.getText()).replaceAll(",", " ");
		String[] keys_array = keys.split("\\s+");
		PageAnalyzer pa = new PageAnalyzer(keys_array);       
		pr.inQueueLink(url_field.getText());
		pr.setTotalPages(pages_requested);

		do {
			pr.retrieve();
			while (!pr.retrievedDocsQIsEmpty()) {
				pp.parse(pr.retrievedDocsQPoll());
				pr.inQueueLink(pp.getNew_links());
			} 
			//if the url_q is empty at this point, 
			//we have run out of sites to crawl
			if ((pr.getUrlQ()).isEmpty()) 
				break;
		} while (pr.pagesLeft() > 0 && !stopped);

		pa.analyze(pp.getParsed_pages());
		if(stopped)
			output_area.append("EXECUTION STOPPED:  \n");
		if(pr.commErrorReceived()){
			output_area.append("Communication Timeout\n");
		}
		if(pr.pagesLeft() > 0) //ran out of urls to parse, note this in output
			output_area.append("THERE WERE " + pa.totalPages() + 
					" PAGE(S) TO CRAWL" + getOutputText(pa.toString() + "\n", start_time));
		else //normal output
			output_area.append(getOutputText(pa.toString(), start_time) + "\n");

		pr.reset();
		pp.reset();
	}

	/**
	 * Crawls the web starting with the url provided using multiple
	 * Threads for retrieving, parsing, and analyzing pages.
	 */
	protected void multiCrawl() {
		long start_time = System.currentTimeMillis();
		output_area.append("MultiThreaded Crawler Started...\n");
		//create blocking queues
		BlockingQueue<String> url_q = new LinkedBlockingQueue<String>();
		url_q.add(url_field.getText());
		BlockingQueue<RawPage> retrieved_docs_q = new LinkedBlockingQueue<RawPage>();
		BlockingQueue<Page> parsed_pages_q = new LinkedBlockingQueue<Page>();

		//create Retriever, Parser, and Analyzer objects
		multi_retriever = new MThreadedPageRetriever(url_q, 
				retrieved_docs_q, Integer.parseInt(pages_field.getText()));
		MThreadedPageParser multi_parser = new MThreadedPageParser(retrieved_docs_q, 
				url_q, parsed_pages_q);  
		String keys = (keywords_field.getText()).replaceAll(",", " ");
		String[] keys_array = keys.split("\\s+");
		multi_analyzer = new MThreadedPageAnalyzer(
				parsed_pages_q, keys_array); 

		//create and start threads
		retriever_thread = new Thread(multi_retriever);
		parser_thread = new Thread(multi_parser);
		analyzer_thread = new Thread(multi_analyzer);
		retriever_thread.start();
		parser_thread.start();
		analyzer_thread.start();

		//wait for the analyzer to complete before displaying results
		try {
			analyzer_thread.join();

		} catch (InterruptedException e) {
			//e.printStackTrace();
		}
		//output_area.setText(getOutputText(analyzer.toString(), start_time));
		output_area.append(getOutputText(multi_analyzer.toString(), start_time) + "\n");
	}


	private String getOutputText(final String results, final long start_time){
		StringBuilder str = new StringBuilder(results);
		str.append("Total Running time: ");
		str.append(String.format("%.4f sec",(float)(System.currentTimeMillis()- start_time)/1000f));
		return str.toString();
	}


	/**
	 * @param args
	 */
	public static void main(String[] args) {
		GUI gui = new GUI();

		WindowListener listener = new WindowAdapter() {
			public void windowClosing(WindowEvent w) {
				System.exit(0);      }    };
				gui.addWindowListener(listener);
	}
}